/* hl2demo info parser */

/* Copyright (c) 2007-2012, ben "boin" bonoeil

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions
   are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimer in the documentation and/or other materials provided
      with the distribution.
    * Neither the name of the <organization> nor the names of its
      contributors may be used to endorse or promote products derived
      from this software without specific prior written permission.

    NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE
    GRANTED BY THIS LICENSE. THIS SOFTWARE IS PROVIDED BY
    ben "boin" bonoeil ''AS IS'' AND ANY EXPRESS OR IMPLIED
    WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
    OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL ben "boin" bonoeil BE LIABLE FOR ANY
    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
    WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <getopt.h>

#define HL2DEMO_MAGIC "HL2DEMO"
#define DEM_PROTOCOL  3
#define NET_PROTOCOL  7         /* pre OB */
/* #define NET_PROTOCOL  15     /\* post OB (current) *\/ */

#include "hl2demo_print.h"
#include "hl2demo_io.h"
#include "utf8simple.h"
#include "levenshtein_distance.h"

static       char * me           = "hl2demo_info";
static const char   dem_magic[8] = HL2DEMO_MAGIC;
static const char   tabstr[]     = "                   ";

typedef struct link_s {
  void * data;
  struct link_s * next;
  int score;
} link_t;

typedef struct hl2demo_player_s {
  struct hl2demo_player_s * next;
  struct hl2demo_player_s * alias;

  int      team;    /* 0:unasign                            */
  int      id;      /* 0:unasign                            */
  char   * steamid; /* Unsupported yet                      */
  link_t * names;   /* Linked in reverse order of apparence */
  char   * warname; /* Usual name (points on names)         */
  char   * curname; /* Last known name (points on names)    */
} hl2demo_player_t;

typedef struct hl2demo_teams_s {
  int id;
  char * prefix;
  char * suffix;
  int  nplayers;
} hl2demo_team_t;

enum {
  HL2DEMO_CHAT_ALL,
  HL2DEMO_CHAT_ALLDEAD,
  HL2DEMO_CHAT_ALLSPEC,
} hl2demo_chat_e;

typedef struct hl2demo_chat_s {
  struct hl2demo_chat_s * next;

  int                type;   /* hl2demo_chat_e */
  hl2demo_player_t * player; /* from           */
  char             * text;   /* say text       */
} hl2demo_chat_t;

struct hl2demo_header_s {
  char    magic[8];        /**< Should be dem_magic "HL2DEMO"  */
  int32_t dem_ver;         /**< Should be DEMO_PROTOCOL (3)    */
  int32_t net_ver;         /**< Should be PROTOCOL_VERSION     */

  char    sv_name[260];    /**< Server name (the game was played in). */
  char    tv_name[260];    /**< Client name (recorder).               */
  char    mp_name[260];    /**< Map name.                             */
  char    gm_name[260];    /**< game directory (com_gamedir).         */

  float   pb_time;         /**< Time of track                  */
  int32_t pb_tick;         /**< # of ticks in track            */
  int32_t pb_fram;         /**< # of frames in track           */
  int32_t sig_len;         /**< length of sigondata in bytes   */

};
typedef struct hl2demo_header_s hl2demo_header_t;

typedef struct hl2demo_s {
  hl2demo_header_t   header;  /* header                    */
  hl2demo_player_t * players; /* reverse order of creation */
  hl2demo_chat_t   * chats;   /* chat list                 */
  hl2demo_team_t team[2];
  link_t * unknown_com;       /* list of unknown commands  */
} hl2demo_t;

static hl2demo_t hl2demo;

static void fatal(int code, const char * fmt, ...)
{
  va_list list;
  error("%s: ",me);
  va_start(list, fmt);
  error_va(fmt,list);
  va_end(list);
  exit (code);
}

static void version(void)
{
  puts(
    "hl2demo_info " VERSION "\n"
    "\n"
    "Copyright (c) 2007-2012, Ben \"Boin\" Bonoeil\n"
    "All rights reserved."
    );
}

static void usage(void)
{
  puts(
    "Usage: hl2demo_info [OPTIONS]  [--] [<input>] [<output>]\n"
    "\n"
    "  -h --help --usage  Display this message and exit\n"
    "  -V --version       Display version and copyright message and exit\n"
    "  -v --verbose       Be more verbose\n"
    "  -q --quiet         Be less verbose\n"
    "  -D --debug         Print debug messages\n"
    "  -i --input=FILE    Set input demo file\n"
    "  -o --output=FILE   Set output info file\n"
    "\n"
    "Default <input> and <output> are respectively stdin and stdout.\n"
    "\n"
    "Report bugs to <boin.ingame [4t] gmail [d0t] com>\n"
    );
}

static hl2demo_player_t *
player_create(const char * name)
{
  hl2demo_player_t * pl = 0;

  if (name) pl = malloc(sizeof(*pl));
  if (pl) {
    memset(pl,0,sizeof(*pl));
    pl->warname = pl->curname = strdup(name);
    if (!pl->warname) {
      free(pl); pl = 0;
    } else {
      pl->names = malloc(sizeof(*pl->names));
      if (!pl->names) {
        free(pl->warname);
        free(pl); pl = 0;
      } else {
        pl->names->next = 0;
        pl->names->data = pl->curname;
      }
    }
  }
  return pl;
}

static hl2demo_player_t *
hl2demo_player_create(hl2demo_t * dm, const char * name)
{
  hl2demo_player_t * player = player_create(name);

  if (player) {
    player->next = dm->players;
    dm->players = player;
  }
  return player;
}

static int
link_add_string(link_t ** link, const char * name)
{
  link_t * newlink = 0;
  if (name) newlink = malloc(sizeof(link_t));
  if (newlink) {
    newlink->data = strdup(name);;
    newlink->score = 0;
    if (newlink->data) {
      newlink->next = *link;
      *link = newlink;
    } else {
      free(newlink);
      newlink = 0;
    }
  }
  return -!newlink;
}

static int
player_addname(hl2demo_player_t * pl, const char * name)
{
  int err = link_add_string(&pl->names, name);
  if (!err) {
    pl->curname = pl->names->data;
  }
  return err;
}

static hl2demo_player_t *
hl2demo_player_addname(hl2demo_t * dem,
                       const char * curname, const char * newname)
{
  hl2demo_player_t * pl;

  for (pl=dem->players; pl && strcmp(curname,pl->curname); pl=pl->next)
    ;

  if (pl && player_addname(pl, newname)) {
    pl = 0;
  }
  return pl;
}

/* qsort compare fucntion for player working array */
static int
cmp_plnames(const void * pa, const void * pb)
{
  const hl2demo_player_t ** a = (const hl2demo_player_t **)pa;
  const hl2demo_player_t ** b = (const hl2demo_player_t **)pb;
  return strcmp_utf8((*a)->warname,(*b)->warname);
}

static int
cmp_plids(const void * pa, const void * pb)
{
  const hl2demo_player_t ** a = (const hl2demo_player_t **)pa;
  const hl2demo_player_t ** b = (const hl2demo_player_t **)pb;
  return (((*a)->team<<24)+(*a)->id) - (((*b)->team<<24)+(*b)->id);
}

static hl2demo_chat_t *
chat_create(hl2demo_player_t * player, const char * text, int type)
{
  hl2demo_chat_t * chat
    = malloc(sizeof(hl2demo_chat_t));

  if (chat) {
    chat->next = 0;
    chat->player = player;
    chat->text = strdup(text);
    chat->type = type;
  }
  return chat;
}

static int
hl2demo_chat_add(hl2demo_t * dm,
                 const char * player, const char * text, int type)
{
  hl2demo_chat_t * chat = 0;
  hl2demo_player_t * pl;
/*   int i; */

  for (pl=dm->players; pl && strcmp(pl->curname,player); pl=pl->next)
    ;
  /* May happen sometime ... Just create it. */
  if (!pl) pl = hl2demo_player_create(dm, player);

  if (pl) {
    chat = chat_create(pl, text, type);
  } else {
    fprintf(stderr,"NOT FOUND player: %s\n",player);
  }

  if (chat) {
    if (!dm->chats) {
      dm->chats = chat;
    } else {
      hl2demo_chat_t * c;
      for (c=dm->chats; c->next; c=c->next)
        ;
      c->next = chat;
    }
  } else {
    fprintf(stderr,"FAILED CHAT player: %s\n",player);
  }

  return -!!chat;
}

static int read_dem_header(FILE * inp, hl2demo_header_t * hd)
{
  int err;

  err = read_data(inp, hd, sizeof(*hd));
  if (err) {
    error ("error reading demo header\n");
  } else if (memcmp(dem_magic, hd->magic, sizeof(dem_magic))) {
    error ("missing magic: not an hl2demo file\n");
    err = -1;
  } else if (hd->dem_ver != DEM_PROTOCOL) {
    warning ("warning: wrong protocol version found %d instead of %d\n",
          hd->dem_ver,DEM_PROTOCOL);
    err = 1;
  } else if (hd->net_ver != NET_PROTOCOL) {
    warning ("warning: wrong protocol version found %d instead of %d\n",
           hd->net_ver,NET_PROTOCOL);
    err = 1;
  }
  return err;
}

static int hl2demo_read_header(hl2demo_t * hl2demo, FILE * inp)
{
  return read_dem_header(inp, &hl2demo->header);
}

static int offset(const void * addr, const void * org) {
  return (const char *)addr - (const char *) org;
}

static void dump_dem_header(const hl2demo_header_t * hd)
{
  message("-------- HEADER\n");
  message("%08X magic   : %s\n",offset(hd->magic,hd),hd->magic);
  message("%08X dem ver : %d\n",offset(&hd->dem_ver,hd),hd->dem_ver);
  message("%08X net ver : %d\n",offset(&hd->net_ver,hd),hd->net_ver);
  message("%08X tv-name : %s\n",offset(hd->tv_name,hd),hd->tv_name);
  message("%08X sv-name : %s\n",offset(hd->sv_name,hd),hd->sv_name);
  message("%08X mp-name : %s\n",offset(hd->mp_name,hd),hd->mp_name);
  message("%08X gm-name : %s\n",offset(hd->gm_name,hd),hd->gm_name);
  message("%08X pb time : %f\n",offset(&hd->pb_time,hd),hd->pb_time);
  message("%08X pb tick : %d\n",offset(&hd->pb_tick,hd),hd->pb_tick);
  message("%08X pb fram : %d\n",offset(&hd->pb_fram,hd),hd->pb_fram);
  message("%08X sig len : %d\n",offset(&hd->sig_len,hd),hd->sig_len);
  message("%08X total   : %d\n",offset(hd+1,hd),sizeof(*hd));
}

/* static void print_quoted(FILE * out, const char * val) */
/* { */
/*   int c; */

/*   fputc('\'',out); */
/*   while (c=getc_utf8(val), c>0) { */
/*     int size = ((c>>29)&3)+1; */
/*     if (c == '\'') { */
/*       fputs("\\'",out); */
/*     } else { */
/*       fwrite(val,1,size,out); */
/*     } */
/*     val += size; */
/*   } */
/*   fputc('\'',out); */
/* } */

/* static void */
/* print_quoted_var(FILE * out, */
/*                  const char * name, const char * val, const char * ends) */
/* { */
/*   fprintf(out,"hl2dem_%s=",name); */
/*   print_quoted(out, val); */
/*   fputs(ends,out); */
/* } */

/* static void */
/* print_unquoted_var(FILE * out, */
/*                    const char * name, const char * val, const char * ends) */
/* { */
/*   fprintf(out,"hl2dem_%s %s%s",name,val,ends); */
/* } */


/* static int */
/* xml_transform(const char * text, FILE * out) */
/* { */
/*   int c; */

/*   while (c = *text, c) { */
/*     int l; */
/*     if (c < 0) return -1; */
/*     l = 1+((c>>29)&3); */
/*     switch (c) { */
/*     case '<': fputs("&lt;",out); break; */
/*     case '>': fputs("&gt;",out); break; */
/*     case '&': fputs("&amp;",out); break; */
/*     default: */
/*       fwrite(text, 1, l, out); */
/*     } */
/*     text += l; */
/*   } */
/*   return 0; */
/* } */

/* static int */
/* print_xml_tag(const char *tag,const char *text, FILE *out) */
/* { */
/*   fprintf(out,"%s<%s>",tab,tag); */
/*   xml_transform(text,out); */
/*   fprintf(out,"</%s>\n",tag); */
/*   return 0; */
/* } */

/* static int */
/* hl2demo_header_print(const hl2demo_header_t * hd, FILE * out) */
/* { */
/*   fprintf(out,"%s<header>\n",tab); */
/*   --tab; */
/*   print_unquoted_var(out,"magic",hd->magic,"\n"); */
/*   print_unquoted_var(out,"tv",hd->tv_name,"\n"); */
/*   print_unquoted_var(out,"server",hd->sv_name,"\n"); */
/*   print_unquoted_var(out,"map",hd->mp_name,"\n"); */
/*   print_unquoted_var(out,"game",hd->gm_name,"\n"); */
/*   ++tab; */
/*   fprintf(out,"%s</header>\n",tab); */
/* /\*   print_unquoted_var(out,"header","yes","\n\n"); *\/ */
/*   return 0; */
/* } */

/* static void */
/* reverse_player_print(link_t * lk, FILE * out) */
/* { */
/*   if (!lk) return; */
/*   reverse_player_print(lk->next, out); */
/*   fputc(' ',out); */
/*   print_quoted(out, (char *)lk->data); */
/* } */

/* static int */
/* hl2demo_player_print(const hl2demo_player_t * pl, FILE * out) */
/* { */
/*   fprintf(out,"hl2dem_player_%X%X %s\n",pl->team,pl->id,pl->warname); */
/*   return 0; */
/* } */

/* static int */
/* hl2demo_team_print(const hl2demo_team_t * team, FILE * out) */
/* { */
/*   fprintf(out,"<team id=%d>\n",team->id); */
/*   switch ((!!team->prefix)*2 + !!team->suffix) { */
/*   case 0: */
/*     fprintf(out,"\n"); break; */
/*   case 1: */
/*     fprintf(out,"%s\n",team->suffix); break; */
/*   case 2: */
/*     fprintf(out,"%s\n",team->prefix); break; */
/*   case 3: */
/*     fprintf(out,"%s ... %s\n",team->prefix,team->suffix); break; */
/*   } */
/*   fprintf(out,"</team>\n"); */
/*   return 0; */
/* } */

/* static int */
/* hl2demo_print_teams(const hl2demo_t * dm, FILE * out) */
/* { */
/*   fputs("<teams>\n",out); */
/*   hl2demo_team_print(dm->team+0, out); */
/*   hl2demo_team_print(dm->team+1, out); */
/*   fputs("</teams>\n",out); */
/*   return 0; */
/* } */

/* static int */
/* hl2demo_print_players(const hl2demo_t * dm, FILE * out) */
/* { */
/*   int err = 0; */
/*   hl2demo_player_t * pl; */

/*   for (pl = dm->players; pl; pl = pl->next) { */
/*     err += !!hl2demo_player_print(pl,out); */
/*   } */
/*   return -!!err; */
/* } */


/* static hl2demo_player_t * */
/* playerof(hl2demo_player_t * pl) */
/* { */
/*   while (pl && pl->alias && pl->alias != pl) */
/*     pl = pl->alias; */

/*   return pl; */
/* } */

static const char * chatstr(int type)
{
  switch (type) {
  case HL2DEMO_CHAT_ALL: return "all";
  case HL2DEMO_CHAT_ALLDEAD: return "dead";
  case HL2DEMO_CHAT_ALLSPEC: return "spec";
  }
  return "?";
}

/* static int */
/* hl2demo_chat_print(hl2demo_chat_t * chat, FILE * out) */
/* { */
/*   fprintf(out, */
/*           "<say from=\"%d\" to=\"%s\">\n" */
/*           "%s\n" */
/*           "</say>\n", */
/*           playerof(chat->player)->id, */
/*           chatstr(chat->type), */
/*           chat->text); */
/*   return 0; */
/* } */

/* static int */
/* hl2demo_print_chats(const hl2demo_t * dm, FILE * out) */
/* { */
/*   int err = 0; */
/*   hl2demo_chat_t * chat; */

/*   fputs("<says>\n",out); */
/*   for (chat = dm->chats; chat; chat = chat->next) { */
/*     err += !!hl2demo_chat_print(chat,out); */
/*   } */
/*   fputs("</says>\n",out); */
/*   return -!!err; */
/* } */

static int
hl2demo_raw_print_all(const hl2demo_t * dm, FILE * out)
{

  /* header */
  {
    const hl2demo_header_t * hd = &dm->header;

    int time = dm->header.pb_time;
    int h,m,s;
    h = time / 3600;
    time %= 3600;
    m = time / 60;
    s = time%60;

    fprintf(out, "H0000 %s\n", hd->gm_name);
    fprintf(out, "H0001 %s\n", hd->sv_name);
    fprintf(out, "H0002 %s\n", hd->tv_name);
    fprintf(out, "H0003 %s\n", hd->mp_name);
    fprintf(out, "H0004 %02d:%02d:%02d\n",h,m,s);
    fprintf(out, "H0005 %d tps\n",
            (int) ( dm->header.pb_tick  / dm->header.pb_time + 0.01 ));
    fprintf(out, "H0006 %d fps\n",
            (int) ( (dm->header.pb_fram + 50)  / dm->header.pb_time  + 0.01) );
  }

  /* teams */
  {
    const hl2demo_team_t *team = dm->team;
    int i;
    for (i=0; i<2; ++i) {
      fprintf(out, "T%d000 %s\n", i+1, team[i].prefix?team[i].prefix:"");
      fprintf(out, "T%d001 %s\n", i+1, team[i].suffix?team[i].suffix:"");
    }
  }

  /* players */
  {
    const hl2demo_player_t * pl;
    for (pl = dm->players; pl; pl = pl->next) {
      fprintf(out, "P%d%03d %s\n", pl->team, pl->id, pl->warname);
    }
  }

  /* chats */
  {
    hl2demo_chat_t * chat;
    int i;
    for (i = 0, chat = dm->chats; chat; chat = chat->next) {
      fprintf(out, "S%04d %c }->%s<-{ %s\n", ++i, *chatstr(chat->type),
              chat->player->curname,chat->text);
    }
  }

  return 0;
}

static int scorestr(const char *a, const char *b)
{
  int i,j,k, score = 0/* , la, lb */;
  int ca,cb;

  if (!strcmp(a,b)) return 1;

  i=j=k=0;
  for(;;) {
    ca = getc_utf8(a+i);
    cb = getc_utf8(b+j);
    i += 1 + ((ca>>29)&3);
    j += 1 + ((cb>>29)&3);
    if (ca != cb || ca<=0 || cb<=0) {
      break;
    }
    ++k;
  }
  score = k;

  /*   for ( a[i] && b[i] && a[i] == b[i]; ++i) */
  /*     ; */
  /*   if (a[i] || b[i]) { */
  /*     score = i; */
  /*     la = strlen(a)-1; */
  /*     lb = strlen(b)-1; */

  /*     for (i=0; i<=la && i<=lb && a[la-i] == b[lb-i]; ++i) */
  /*       ; */
  /*     score += i; */

  /*   } */
  /*   printf("score <%s> <%s> = %d\n",a,b,score); */
  return score;

}


/* Test if a rectangle string area is only composed by similar lines */
static int
test_rect(hl2demo_player_t ** pls, const int l, const int h)
{
  int i;
  for (i=0; i<h; ++i) {
    if (strlen_utf8(pls[i]->warname)<l) return 0;
  }
  for (i=0; i<h; ++i) {
    if (strncmp_utf8(pls[0]->warname,pls[i]->warname,l)) return 0;
  }
  return 1;
}

/* /\* Get min and max string length *\/ */
/* static void */
/* get_length(hl2demo_player_t ** pls, int npl, int *min, int *max)  */
/* { */
/*   int i, m, M; */

/*   m = M = (npl>0) ? strlen_utf8(pls[0]->warname) : 0; */
/*   for (i=1; i<npl; ++i) { */
/*     int l = strlen_utf8(pls[i]->warname); */
/*     if (l<m) m = l; */
/*     if (l>M) M = l; */
/*   } */
/*   *min = m; */
/*   *max = M; */
/* } */

static int r_find(hl2demo_player_t ** pls, int npl, int l, int h)
{
  int res = 0, res2;

  if (h<=npl && test_rect(pls,l,h)) {
    res = ((l*h)<<10) | h;
    res2 = r_find(pls,npl,l+1,h);
    if (res2 > res) res = res2;
    res2 = r_find(pls,npl,l,h+1);
    if (res2 > res) res = res2;
  }
  return res;
}

static int find_best_match(hl2demo_player_t ** pls, int npl,
                           int * pi, int * pw, int * ph)
{
  const int min_w = 2, min_h = 3;
  int i, j, res;

  *pi = 0; *pw = 0; *ph = 0;

  qsort(pls,npl,sizeof(*pls),cmp_plnames);

  res =
    r_find(pls, npl, min_w, min_h);

  for (i=0, j=1; j<npl; ++j) {
    int res2 =
      r_find(pls+j, npl-j, min_w, min_h);
    if (res2 > res) {
      i = j;
      res = res2;
    }
  }

  if (res) {
    *pi = i;
    *ph = res&0x3FF;
    *pw = (res>>10) / ph[0];
  }
  return res;
}

static void
hl2demo_swap_teams(hl2demo_t * dm)
{
  hl2demo_player_t * pl;
  hl2demo_team_t tmp = dm->team[0];
  dm->team[0] = dm->team[1];
  dm->team[1] = tmp;

  for (pl=dm->players; pl; pl=pl->next) {
    if (pl->team == 1) {
      pl->team = 2;
    } else if (pl->team == 2) {
      pl->team = 1;
    }
  }
}

static char *
stripname(const char * name)
{
/*   const char * save = name; */
  const char * start = 0, * notspace;
  char * newname;
  int c,l;

  if (!name) return 0;

  /* skip starting spaces */
  while (c = getc_utf8(name), c>0 && isspace_utf8(c)) {
    name += 1+((c>>29)&3);
  }
  start = name;
/*   fprintf(stderr, */
/*           "STRIP:[%s]\n" */
/*           "START:[%s]\n",save, start); */

  name += 1+((c>>29)&3);
  notspace = name;

  /* locate last non-space char */
  while (c = getc_utf8(name), c > 0) {
/*     fprintf(stderr, */
/*             ".....:[%s], %d\n",name, c&0xFFFFFF); */
    name += 1+((c>>29)&3);
    if (!isspace_utf8(c)) notspace = name;
  }

/*   fprintf(stderr, */
/*           "SPACE:[%s]\n",notspace); */
  l = notspace-start;
/*   fprintf(stderr, */
/*           "LEN  = %d\n",l); */

  newname = malloc(l+1);
  if (newname) {
    memcpy(newname, start, l);
    newname[l] = 0;
  }
  return newname;
}

static void
hl2demo_assign_team(hl2demo_t * dm)
{
  int npl,npl_save,i,j/*,k,g,besti,bestj ,bestscore */;
  hl2demo_player_t * pl, **pls, **pls_save = 0;

  /* Count connected players */
  for (npl=0, pl=dm->players; pl; pl=pl->next, ++npl)
    ;
  if (npl<1) return;

  /* Create indirect player working array */
  pls_save = pls = malloc(npl * sizeof(*pls));
  if (!pls) return;
  for (npl=0, pl=dm->players; pl; pl=pl->next, ++npl) {
    pls[npl] = pl;
  }

  /* Remove duplicate players */
  qsort(pls,npl,sizeof(*pls),cmp_plnames);
  for (j=i=1;i<npl;++i) {
    if (strcmp_utf8(pls[i-1]->warname,pls[i]->warname)) {
      pls[j++] = pls[i];
    } else {
/*       printf("#DUPLICATE %s\n",pls[i]->warname); */
    }
  }
  npl_save = npl = j;

  for (j=0; j<2; ++j) {
    int l=0, w=0, h=0;

    dm->team[j].id = 0; /* unassign yet */
    if (npl<=0) break;
    if (!find_best_match(pls,npl,&l,&w,&h)) continue;

    /* Found a <h> player team at line <l> with tag length of <w> chars */
    {
      char temp[512];
      strncpy(temp, pls[l]->warname, 511);
      temp[511] = 0;
      skipfw_utf8(temp,w)[0] = 0;
      dm->team[j].id = j+1;
      dm->team[j].prefix = stripname(temp);
      dm->team[j].nplayers = h;
    }

    /* Copy this team players at the beginning of working array */
    for (i=0;i<h;++i) {
      pl = pls[i];
      pls[i] = pls[i+l];
      pls[i+l] = pl;
      pls[i]->id = i+1;
      pls[i]->team = dm->team[j].id;
    }

    /* Let's continue */
    pls += h;
    npl -= h;
  }

  npl = npl_save;
  pls = pls_save;
  if (npl) {
    qsort(pls,npl,sizeof(*pls),cmp_plids);
    for (i=0; i<npl-1; ++i) {
      pls[i]->next = pls[i+1];
    }
    pls[i]->next = 0;
    dm->players = pls[0];
  }

  /* Order teams */
  if (dm->team[0].prefix) {
    if (dm->team[1].prefix &&
        strcmp_utf8(dm->team[0].prefix,dm->team[1].prefix) > 0) {
      hl2demo_swap_teams(dm);
    }
  } else if (dm->team[1].prefix) {
    hl2demo_swap_teams(dm);
  }

  free(pls_save);
}

/* static void */
/* do_levenshtein(const hl2demo_t * dm) */
/* { */
/*   hl2demo_player_t *pl, *otherpl; */
/*   link_t * lk, * cur; */

/*   for (pl=dm->players; pl; pl=pl->next) { */
/*     for (cur=pl->names; cur; cur=cur->next) { */
/*       printf(">>%s\n", (const char *)cur->data); */
/*       for (otherpl=dm->players; otherpl; otherpl=otherpl->next) { */
/*         for (lk=otherpl->names; lk; lk=lk->next) { */
/*           printf("- %04d %s\n", */
/*                  levenshtein_distance(cur->data, lk->data), */
/*                  (const char *)lk->data); */
/*         } */
/*       } */
/*       printf("\n"); */
/*     } */
/*   } */
/* } */

static void
hl2demo_find_warname(const hl2demo_t * dm)
{
  hl2demo_player_t *pl;

  for (pl=dm->players; pl; pl=pl->next) {
    link_t * lk, * cur, * best;
    int bestscore, score;

    if (!pl->names || !pl->names->next) {
      continue;
    }

    for (best=cur=pl->names,bestscore=-1; cur; cur=cur->next) {
      hl2demo_player_t *otherpl;
      for (otherpl=dm->players, score=0; otherpl; otherpl=otherpl->next) {
        if (pl == otherpl) continue;
        for (lk=otherpl->names; lk; lk=lk->next) {
          int herescore = scorestr(cur->data,lk->data);
          score += herescore<<cur->score;
        }
      }
      printf("\n");

      cur->score = score;

      if (score > bestscore) {
        bestscore = score;
        best = cur;
      }
    }
    pl->warname = best->data;
  }
}

static int
find_strings(FILE * inp, const char ** strs, int max)
{
  int pos = 0;

  clearerr(inp);
  pos = ftell(inp);
  if (pos == -1) return -1;

  while (1) {
    int j, c;

    c = fgetc(inp);
    if (c == EOF) return max;

    for (j=0; j<max; ++j) {
      const char * s = strs[j];
      if (c == s[0]) {
        int i;
        for (i=1; s[i] && (c=fgetc(inp)) == s[i]; ++i)
          ;
        if (!s[i]) {
          if (fseek(inp,pos,SEEK_SET) == -1) return -1;
          return j;
        }
        if (fseek(inp,pos+1,SEEK_SET) == -1) return -1;
        clearerr(inp);
      }
    }
    ++pos;
  }
  return max;
}

/* static size_t find_string(FILE * inp, const char * s) */
/* { */
/*   const int l = strlen(s); */
/*   int i = 0, c; */

/*   while (i < l) { */
/*     c = fgetc(inp); */
/*     if (c == EOF) return -1; */
/*     if (c == s[i]) { */
/*       ++i; */
/*     } else { */
/*       if (i) { */
/*         fseek(inp,-i,SEEK_CUR); */
/*         i = 0; */
/*       } */
/*     } */
/*   } */
/*   fseek(inp,-i,SEEK_CUR); */
/*   return ftell(inp); */
/* } */

/* static int skip_string(FILE * inp) */
/* { */
/*   int c; */
/*   while (c = fgetc(inp), (c != EOF && c != 0)) */
/*     ; */
/*   return c; */
/* } */


static char * read_string(FILE *inp)
{
  int c, l;
  fpos_t pos;
  char * s = 0;

  if (fgetpos(inp, &pos) != -1) {
    for (l=0; c=fgetc(inp), (c != EOF && c != 0); ++l)
      ;
    if (fsetpos(inp, &pos) != -1) {
      s = malloc(l+1);
      if (s) {
        fread(s,1,l+1,inp);
        s[l] = 0;
      }
    }
  }
  return s;
}

static int
hl2demo_parser(hl2demo_t * dm, FILE * inp)
{
  int i, err = -1, restart = 0;
  long rspos= 0;

  static const char * all[] = {
    "#Game_",
    "Cstrike_",
  };
  const int max = sizeof(all)/sizeof(*all);

  do {
    char * s = 0, * a = 0, * b = 0;

    i = find_strings(inp, all, max);
    if (i<0 || i>=max) {
      err = i != max;
      break;
    }
    s = read_string(inp);
    if (!s) {
      break;
    }

    if (!strcmp(s,"#Game_connected")) {
      hl2demo_player_t * pl;
      /* New player connect : a=>player-name b=>unused */
      a = read_string(inp);
      b = 0;
      /* printf("#CONNECT<%s>\n",a); */
      pl = hl2demo_player_create(dm,a);
      if (!pl) {
        break;
      }
    } else if (!strcmp(s,"#Game_will_restart_in")) {
      /* Game restart : a=>time b=>unit */
      long pos = ftell(inp);
      int delta = pos-rspos;
      rspos = pos;

      a = read_string(inp);
      b = read_string(inp);
      if (delta > 50) {
        ++restart;
        if (restart == 3) {
          hl2demo_player_t * pl;
          for (pl=dm->players; pl; pl=pl->next) {
            pl->names->score = 1;
          }
        }
      }
    } else if (!strcmp(s,"#Game_scoring")) {
      //      printf("SCORING<>\n");
    } else if (!strcmp(s,"Cstrike_Name_Change")) {
      /* New player connect : a=>old name b=>new name */
      a = read_string(inp);
      b = read_string(inp);
      hl2demo_player_addname(dm,a,b);
    } else if (strstr(s,"Cstrike_Chat_") == s) {
      int type;
      /* Cstrike_Chat_All
         Cstrike_Chat_AllSpec
         Cstrike_Chat_AllDead
         a =>Player b =>saying
      */
      if (!strcmp(s,"Cstrike_Chat_AllDead")) {
        type = HL2DEMO_CHAT_ALLDEAD;
      } else if (!strcmp(s,"Cstrike_Chat_AllSpec")) {
        type = HL2DEMO_CHAT_ALLSPEC;
      } else {
        type = HL2DEMO_CHAT_ALL;
      }
      a = read_string(inp);
      b = read_string(inp);
      hl2demo_chat_add(dm,a,b,type);
      /* printf("#CHAT_%s<%s><%s>\n",s+13,a,b); */
    } else {
      link_add_string(&dm->unknown_com,s);
    }
    free(s); s=0;
    free(a); a=0;
    free(b); b=0;
  } while (1);

  return -!!err;
}

static int opt_quiet;
static int opt_debug;
static int opt_verbose;

int main (int argc, char *argv[])
{
  int i;
  FILE * inp   = stdin, * out   = stdout;
  char * iname = 0,     * oname = 0;

  static struct option lopts[] = {
    /* name, has_arg, *flag, val */
    { "quiet",   0, 0, 'q' },
    { "verbose", 0, 0, 'v' },
    { "debug",   0, 0, 'D' },
    { "version", 0, 0, 'V' },
    { "help",    0, 0, 'h' },
    { "usage",   0, 0, 'h' },
    { "input",   0, 0, 'i' },
    { "output",  0, 0, 'o' },
    { 0,0,0,0 }
  };
  argv[0] = me;
  while ( i = getopt_long(argc, argv, "qvDVhi:o:", lopts, 0), i != -1) {
    switch (i) {
    case 'D': opt_debug   = 1; break;
    case 'q': opt_quiet   = 1; break;
    case 'v': opt_verbose = 1; break;
    case 'V': version();       return 0;
    case 'h': usage();         return 0;
    case 'i': iname = optarg; break;
    case 'o': oname = optarg; break;
    case '?':
    default:
      error("%s: invalid option -- `%c'\n", me, i);
      return -1;
    }
  }

  msgfile = opt_quiet ? 0 : stdout;
  dbgfile = opt_debug ? msgfile : 0;
  errfile = stderr;
  wrnfile = opt_quiet ? 0 : errfile;

  i = optind;
  if (i < argc)
    iname = argv[i++];
  if (i < argc)
    oname = argv[i++];
  if (i < argc)
    fatal(-1, "too many arguments\n");

  if (!iname || !strcmp(iname,"-")) iname = "/dev/stdin";
  else {
    inp = fopen(iname,"rb");
    if (!inp) fatal(-1, "`%s' -- %s\n", iname, strerror(errno));
  }

  if (!oname || !strcmp(oname,"-")) oname = "/dev/stdout";
  else {
    out = fopen(oname,"wt");
    if (!out) fatal(-1, "`%s' -- %s\n", oname, strerror(errno));
  }


  debug ("quiet:%d debug:%d verbose:%d\n",
         opt_quiet, opt_debug, opt_verbose);
  debug ("inp name: '%s'\n",iname);
  debug ("out name: '%s'\n",oname);

  if (hl2demo_read_header(&hl2demo,inp) < 0) {
    if (errno) {
      fatal (-1, "`%s' -- %s\n", iname, strerror(errno));
    } else {
      fatal (-1, "`%s' -- not a hl2demo\n", iname);
    }
  }
  dump_dem_header(&hl2demo.header);

  if (hl2demo_parser(&hl2demo,inp)) {
    if (errno) {
      fatal (-1, "`%s' -- %s\n", iname, strerror(errno));
    } else {
      fatal (-1, "`%s' -- parser failed\n", iname);
    }
  }

  hl2demo_find_warname(&hl2demo);
  hl2demo_assign_team(&hl2demo);
  hl2demo_raw_print_all(&hl2demo, out);

  if (inp != stdin)  fclose(inp);
  if (out != stdout) fclose(out);

  return 0;
}
